package com.jeasonzhao.commons.parser.lex;

import java.io.FileNotFoundException;
import java.io.IOException;

import com.jeasonzhao.commons.basic.StringPair;

public class BaseTokenizer extends LexTokenizer
{
    private LexTokenizerConfig m_LexTokenizerConfig = new LexTokenizerConfig();
    public BaseTokenizer()
    {
        super();
    }

    public LexTokenizerConfig getConfig()
    {
        return m_LexTokenizerConfig;
    }

    public BaseTokenizer(LexTokenizerConfig tk)
    {
        super();
        if(null != tk)
        {
            m_LexTokenizerConfig = tk;
        }
    }

    public BaseTokenizer(LexTokenizerConfig tk,String str)
    {
        super(str);
        if(null != tk)
        {
            m_LexTokenizerConfig = tk;
        }

    }

    public BaseTokenizer(BaseTokenizer base,String str)
    {
        super();
        if(base != null)
        {
            this.m_LexTokenizerConfig = base.m_LexTokenizerConfig;
        }
        this.setInitStatement(str);
    }

    public BaseTokenizer(String str)
    {
        super(str);
    }

    public void loadFile(String strFileName)
        throws FileNotFoundException,IOException
    {
        java.io.InputStream stream = new java.io.FileInputStream(strFileName);
        try
        {
            loadStream(stream);
        }
        finally
        {
            stream.close();
        }
    }

    public void loadStream(java.io.InputStream stream)
        throws IOException
    {
        java.io.BufferedReader buf = new java.io.BufferedReader(new java.io.InputStreamReader(stream));
        StringBuffer b = new StringBuffer();
        String strLine = null;
        while((strLine = buf.readLine()) != null)
        {
            b.append(strLine + "\n");
        }
        this.setInitStatement(b.toString());
    }

    protected LexToken getPrefixBlankToken()
    {
        LexToken tokenBlank = createToken();
        StringBuffer strBlanks = new StringBuffer();
        char c = current();
        while(c != END && Character.isWhitespace(c))
        {
            strBlanks.append(c);
            c = next();
        }
        if(null != strBlanks && strBlanks.length() > 0)
        {
//            System.out.println("Blanks ["+strBlanks.length()+"]"+
//                               this.getCurrentPosition()+"/"+this.getLengthOfStatement()+
//                               "Current="+((int)this.Current()));
            tokenBlank.setToken(strBlanks.toString());
            tokenBlank.setType(LexTokenTypes.BLANK);
            tokenBlank.setEndRowIndex(this.getCurrentRowIndex());
            tokenBlank.setPrevBlanks(null);
            return tokenBlank;
        }
        else
        {
            return null;
        }
    }

    public LexToken nextToken()
        throws LexException
    {
        LexToken tp = this.getPrefixBlankToken();
        if(current() == END)
        {
            return null == tp
                || tp.getPrevBlanks() == null
                || tp.getPrevBlanks().getToken() == null
                || tp.getPrevBlanks().getToken().length() < 1
                ? null : tp;
        }
        LexToken token = parseToken();
        if(token != null)
        {
            token.setEndRowIndex(this.getCurrentRowIndex());
            token.setPrevBlanks(tp);
        }
        return token;
    }

    private LexToken parseToken()
        throws LexException
    {
        char c = current();
        if(c == END)
        {
            return null;
        }
        LexToken token = createToken();
        for(int n = 0;null != m_LexTokenizerConfig.getLineComnmentFlags()
            && n < m_LexTokenizerConfig.getLineComnmentFlags().size();n++)
        {
            String strMask = m_LexTokenizerConfig.getLineComnmentFlags().get(n);
            if(this.isNextStringMatchs(strMask))
            {
                return this.parseLineComment(token,strMask);
            }
        }
        //�����ַ���
        for(int n = 0;null != m_LexTokenizerConfig.getSpecialSymbols()
            && n < m_LexTokenizerConfig.getSpecialSymbols().size();n++)
        {
            String strMask = m_LexTokenizerConfig.getSpecialSymbols().get(n);
            if(this.isNextStringMatchs(strMask))
            {
                token.setType(LexTokenTypes.SPECIAL_SYMBOL);
                token.setToken(strMask);
                next(strMask.length());
                return token;
            }
        }
        if(m_LexTokenizerConfig.isNamePrefix(c))
        {
//            System.out.println("["+c+"]name from:>"+m_strNamePrefixs);
            return this.parseName(token);
        }
        //��ע��
        for(int n = 0;null != m_LexTokenizerConfig.getCommentBlockPairs()
            && n < m_LexTokenizerConfig.getCommentBlockPairs().size();n++)
        {
            StringPair mask = m_LexTokenizerConfig.getCommentBlockPairs().get(n);
            if(this.isNextStringMatchs(mask.getFirst()))
            {
                return this.parseBlockComment(token,mask.getFirst(),mask.getSecond());
            }
        }
        //�������
        for(int n = 0;null != m_LexTokenizerConfig.getSpecialNamePairs()
            && n < m_LexTokenizerConfig.getSpecialNamePairs().size();n++)
        {
            StringPair mask = m_LexTokenizerConfig.getSpecialNamePairs().get(n);
            if(this.isNextStringMatchs(mask.getFirst()))
            {
                LexToken t = this.parseSpecialName(token,mask.getFirst(),mask.getSecond());
                if(null != t)
                {
                    return t;
                }
            }
        }

        char cnext = peekNext();
        //����ж�
        if(Character.isLetter(c))
        {
            return this.parseName(token);
        }
        else if(Character.isDigit(c) ||
                (c == '.' && Character.isDigit(cnext)))
        {
//            System.out.println("[" + c + "]parseNumber from:>" + c);
            return this.parseNumber(token);
        }
        else if(c == '\"' || c == '\'')
        {
            return this.parseString(false,token);
        }
        else if(c == m_LexTokenizerConfig.getMultiLineStringPrefix() &&
                (this.peekNext() == '\"' || this.peekNext() == '\''))
        {
            return this.parseString(true,token);
        }
        else
        {
            return this.parseSpecial(token);
        }
    }

    private LexToken parseSpecialName(LexToken token,String strBeginMask,String strEndMask)
        throws LexException
    {
        int nIndexId = this.getCurrentPosition(); //���浱ǰ��λ�ã��������������л���
        this.next(strBeginMask.length());
        LexToken nextToken = this.nextToken();
        if(this.isNextStringMatchs(strEndMask) && nextToken != null && nextToken.isName())
        {
            token.setType(LexTokenTypes.SPECIAL_SYMBOL);
            token.setToken((token.getToken() == null ? "" : token.getToken())
                           + strBeginMask
                           + nextToken.getToken()
                           + strEndMask
                );
            this.next(strEndMask.length());
            return token;
        }
        else
        {
            this.backTo(nIndexId);
            return null;
        }
    }

    private LexToken parseSpecial(LexToken token)
        throws LexException
    {
        if((current() == '+' || current() == '-')
           && Character.isDigit(this.peekNext()))
        {
            return this.parseNumber(token);
        }
        token.setType(LexTokenTypes.SPECIAL);
        token.setToken("" + current());
        next();
        return token;
    }

    private String descapeString(String strToken)
    {
        return null == strToken ? null :
            strToken.replaceAll("\\\\\"","\"")
            .replaceAll("\\\\n","\n")
            .replaceAll("\\\\r","\r")
            .replaceAll("\\\\\\\\","\\\\")
            ;
    }

    private LexToken parseString(boolean isMulti,LexToken token)
        throws LexException
    {
        char cBegin = current();
        if(isMulti)
        {
            cBegin = next();
        }
        char cEnd = cBegin == '\"' ? '\"' : '\'';
        token.setType(cBegin == '\"' ? LexTokenTypes.STRING_QUOTED : LexTokenTypes.STRING);
        String strToken = "";
        next();
        char currentLineLast = END;
        int ncountf = 0;
        while(current() != END)
        {
            char c = current();
            if(c == cEnd)
            {
                if(ncountf % 2 == 0)
                {
                    break;
                }
            }
            if(c == '\\')
            {
                ncountf++;
            }
            else
            {
                ncountf = 0;
            }
//            System.out.println(">>"+ncountf+">>>>"+c);
            if(c == '\n')
            {
                if(isMulti == false && m_LexTokenizerConfig.getMultiLineStringSuffix() != END)
                {
                    if(END == currentLineLast || currentLineLast != m_LexTokenizerConfig.getMultiLineStringSuffix())
                    {
                        throwException(strToken,"Should not contains line - break in String ");
                    }
                    int nl = strToken.lastIndexOf(m_LexTokenizerConfig.getMultiLineStringSuffix());
                    if(nl >= 0)
                    {
                        strToken = strToken.substring(0,nl); //+ strToken.substring(nl + 1);
                    }
                }
                else if(isMulti == false)
                {
                    throwException(strToken,"Should not contains line-break in String[Non-Multiple-line]");
                }
                currentLineLast = END;
            }
            strToken += c;
            if(Character.isWhitespace(c) == false)
            {
                currentLineLast = c;
            }
            next();
        }
        if(current() != cEnd)
        {
            throwException(token,"Could not match the begin and the end tag of String");
        }
        if(isMulti)
        {
            token.setPrefix("" + this.m_LexTokenizerConfig.getMultiLineStringPrefix() + cBegin);
        }
        else
        {
            token.setPrefix("" + cBegin);
        }
        token.setSuffix("" + cEnd);
        token.setToken(descapeString(strToken));
        next();
        return token;
    }

    private LexToken parseBlockComment(LexToken token,String strBeginComment,String strEndComment)
        throws LexException
    {
        token.setType(LexTokenTypes.COMMENT_BLOCK);
        StringBuffer strToken = new StringBuffer();
        int nLastLength = strEndComment.length();
        int nMinLenth = strBeginComment.length() + strEndComment.length();
        boolean bFound = false;
        while(this.current() != END)
        {
            if(strToken.length() >= nMinLenth
               && getPrevString(nLastLength).equals(strEndComment))
            {
                bFound = true;
                break;
            }
            strToken.append(current());
            next();
        }
        if(bFound == false)
        {
            throwException(token,"Could not found the end of comments tag " + strBeginComment + "..." + strEndComment);
        }
        token.setPrefix(strBeginComment);
        token.setToken(strToken.substring(strBeginComment.length(),strToken.length() - strEndComment.length()));
        token.setSuffix(strEndComment);
        return token;
    }

    private LexToken parseLineComment(LexToken token,String strMask)
    {
        char c = current();
        StringBuffer strToken = new StringBuffer();
        while(c != '\n' && c != END)
        {
            strToken.append(c);
            c = next();
        }
        token.setToken(strToken.substring(strMask.length()));
        token.setPrefix(strMask);
        token.setType(LexTokenTypes.COMMENT_LINE);
        return token;
    }

    private boolean isSpecial(char c)
    {
        return Character.isLetterOrDigit(c) == false &&
            Character.isWhitespace(c) == false;
    }

    protected LexToken parseName(LexToken token)
        throws LexException
    {
        token.setType(LexTokenTypes.NAME);
        StringBuffer strTokenContent = new StringBuffer();
        strTokenContent.append(current());
        boolean isPrevCharIsSpecial = isSpecial(current());
        char c = next();
        while(c != END)
        {
            if(!m_LexTokenizerConfig.isNameCharacter(c))
            {
                break;
            }
            strTokenContent.append(c);
            c = next();
            isPrevCharIsSpecial = isPrevCharIsSpecial == false ? false : isSpecial(c);
        }
        for(int n = 0;null != m_LexTokenizerConfig.getLongNameDelimiters()
            && n < m_LexTokenizerConfig.getLongNameDelimiters().size();n++)
        {
            String strDilimiter = m_LexTokenizerConfig.getLongNameDelimiters().get(n);
            int nIndexId = this.getCurrentPosition();
            if(this.isNextStringMatchs(strDilimiter))
            {
                next(strDilimiter.length());
                LexToken nextName = this.nextToken();
                if(nextName.isName() == false)
                {
                    backTo(nIndexId);
                }
                else
                {
                    token.setType(LexTokenTypes.LONG_NAME);
                    strTokenContent.append(strDilimiter);
                    strTokenContent.append(nextName.getToken());
                    break;
                }
            }
        }
        token.setToken(strTokenContent.toString());
        return token;
    }

    protected void throwException(String strCurrentToken,String strMessage)
        throws LexException
    {
        throw new LexException(getCurrentRowIndex(),getCurrentColumnIndex(),strMessage +
                               (null == strCurrentToken || strCurrentToken.trim().length() < 1 ? "" : "��" + strCurrentToken));
    }

    protected void throwException(LexToken tk,String strMessage)
        throws LexException
    {
        throw new LexException(tk.getBeginRowIndex(),tk.getBeginRowIndex(),strMessage);
    }

    protected LexToken parseNumber(LexToken token)
        throws LexException
    {
        token.setType(LexTokenTypes.NUMBER);
        StringBuffer strToken = new StringBuffer();
        strToken.append(current());
        int nDotCount = 0;
        int eCount = 0;
        char c = next();
        boolean bNeedMinusOrPlus = false;
        while(c != END)
        {
            if(Character.isDigit(c))
            {
                strToken.append(c);
            }
            else if(c == '.')
            {
                nDotCount++;
                if(nDotCount > 1)
                {
                    throwException(strToken.toString(),"More than one dot in number");
                }
                if(Character.isDigit(peekNext()) == false)
                {
                    throwException(strToken.toString(),"There sould be some number after dot");
                }
                token.setType(LexTokenTypes.DOUBLE);
                strToken.append(c);
            }
            else if(c == 'e' || c == 'E')
            {
                eCount++;
                if(eCount > 1)
                {
                    throwException(strToken.toString(),"More than on \"E\" in number");
                }
                bNeedMinusOrPlus = true;
                if(Character.isDigit(peekNext()) == false
                   && peekNext() != '+'
                   && peekNext() != '-')
                {
                    throwException(strToken.toString(),"There should be \"+\" or \"-\" after the sign \"E\" in number");
                }
                token.setType(LexTokenTypes.DOUBLE);
                strToken.append(c);
            }
            else if(c == '+' || c == '-')
            {
                if(bNeedMinusOrPlus)
                {
                    strToken.append(c);
                }
                else
                {
                    break;
                }
            }
            else
            {
                break;
            }
            c = next();
        }
        if(this.m_LexTokenizerConfig.isUsingPrecentSign() && c == '%')
        {
            strToken.append(c);
            next();
            token.setType(LexTokenTypes.DOUBLE);
        }
        token.setToken(strToken.toString());
        return token;

    }

    public LexTokenCollection getTokens(boolean bHaveComment,boolean bHasBlanks)
        throws LexException
    {
        LexTokenCollection ret = new LexTokenCollection();
        LexToken token = null;
        while((token = this.nextToken()) != null && token.getType() != LexTokenTypes.NULL)
        {
            if(token.isComment() && bHaveComment == false)
            {
                continue;
            }
            if(token.getType() == LexTokenTypes.BLANK && bHasBlanks == false)
            {
                continue;
            }
            ret.add(token);
        }
        return ret;
    }
}
